From a352e519dbffa2b90b1a6ddd9080b2eb891e224d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Florian=20M=C3=BCllner?= Date: Thu, 19 Dec 2019 23:07:55 +0100 Subject: [PATCH] environment: Add async ease variants Ease functions can be used nested, and often need to complete before performing an action. This is currently handled by providing onComplete/onStopped callbacks, which can get tedious when nesting. To make these cases more convenient, add async variants that return a promise that is resolved when the transition completes, so that callers can await the result instead. In case the transition was interrupted, the promise is rejected with a CANCELLED error, so that it stops a promises chain and can easily be filtered out in catch handlers. Base on an original patch by Marco Trevisan. Part-of: --- js/ui/environment.js | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/js/ui/environment.js b/js/ui/environment.js index 1d9d057c0..83743eeb2 100644 --- a/js/ui/environment.js +++ b/js/ui/environment.js @@ -17,6 +17,7 @@ import St from 'gi://St'; import * as SignalTracker from '../misc/signalTracker.js'; import {adjustAnimationTime} from '../misc/animationUtils.js'; +import {logErrorUnlessCancelled} from '../misc/errorUtils.js'; const sessionSignalHolder = new SignalTracker.TransientSignalHolder(); @@ -61,14 +62,24 @@ function _makeEaseCallback(params, cleanup) { const onStopped = params.onStopped; delete params.onStopped; - return isFinished => { + const {promise, resolve, reject} = Promise.withResolvers(); + const callback = isFinished => { cleanup?.(); if (onStopped) onStopped(isFinished); if (onComplete && isFinished) onComplete(); + + if (isFinished) { + resolve(); + } else { + reject(new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.CANCELLED, 'Transition was stopped before completing')); + } }; + + return {promise, callback}; } function _makeEasePrepareAndCleanup(duration) { @@ -146,7 +157,7 @@ function _easeActor(actor, params) { const easingDuration = actor.get_easing_duration(); const {prepare, cleanup} = _makeEasePrepareAndCleanup(easingDuration); - const callback = _makeEaseCallback(params, cleanup); + const {promise, callback} = _makeEaseCallback(params, cleanup); // cancel overwritten transitions const animatedProps = Object.keys(params).map(p => p.replace('_', '-', 'g')); @@ -177,6 +188,8 @@ function _easeActor(actor, params) { } else { callback(true); } + + return promise; } function _easeAnimatableProperty(animatable, propName, target, params) { @@ -222,7 +235,7 @@ function _easeAnimatableProperty(animatable, propName, target, params) { duration = 0; const {prepare, cleanup} = _makeEasePrepareAndCleanup(duration); - const callback = _makeEaseCallback(params, cleanup); + const {promise, callback} = _makeEaseCallback(params, cleanup); // cancel overwritten transition animatable.remove_transition(propName); @@ -236,7 +249,7 @@ function _easeAnimatableProperty(animatable, propName, target, params) { prepare?.(); callback(true); - return; + return promise; } const pspec = animatable.find_property(propName); @@ -261,6 +274,7 @@ function _easeAnimatableProperty(animatable, propName, target, params) { transition.connectObject('stopped', (t, finished) => callback(finished), sessionSignalHolder); + return promise; } // Add some bindings to the global JS namespace @@ -318,15 +332,27 @@ Clutter.Actor.prototype.set_easing_delay = function (msecs, params = {}) { }; Clutter.Actor.prototype.ease = function (props) { - _easeActor(this, props); + _easeActor(this, props).catch(logErrorUnlessCancelled); }; Clutter.Actor.prototype.ease_property = function (propName, target, params) { - _easeAnimatableProperty(this, propName, target, params); + _easeAnimatableProperty(this, propName, target, params).catch(logErrorUnlessCancelled); }; St.Adjustment.prototype.ease = function (target, params) { // we're not an actor of course, but we implement the same // transition API as Clutter.Actor, so this works anyway - _easeAnimatableProperty(this, 'value', target, params); + _easeAnimatableProperty(this, 'value', target, params).catch(logErrorUnlessCancelled); +}; + +Clutter.Actor.prototype.easeAsync = async function (props) { + await _easeActor(this, props); +}; +Clutter.Actor.prototype.ease_property_async = async function (propName, target, params) { + await _easeAnimatableProperty(this, propName, target, params); +}; +St.Adjustment.prototype.easeAsync = async function (target, params) { + // we're not an actor of course, but we implement the same + // transition API as Clutter.Actor, so this works anyway + await _easeAnimatableProperty(this, 'value', target, params).catch(() => {}); }; Clutter.Actor.prototype[Symbol.iterator] = function* () { -- 2.39.5